using System;

namespace WowAceUpdater
{
    public class VersionNumber : IComparable, IFormattable
    {
        #region Enums

        #region ExternalsType enum

        public enum ExternalsType
        {
            WITH = 0,
            WITHOUT,
            UNKNOWN,
        } ;

        #endregion

        #region StabilityType enum

        public enum StabilityType
        {
            STABLE = 0,
            UNSTABLE,
            UNKNOWN
        } ;

        #endregion

        private enum compare
        {
            LESSER,
            GREATER,
            EQUAL
        } ;

        #endregion

        #region Constants

        private const string UNKNOWN_EXTERNALS_STR = "?";

        const string UNKNOWN_STABILITY_STR = "?";



        const string STABLE_STR = "S";


        const string UNSTABLE_STR = "";


        private const string WITH_EXTERNALS_STR = "+";

        private const string WITHOUT_EXTERNALS_STR = "-";

        #endregion

        #region Readonly

        public static readonly VersionNumber NO_VERSION = new VersionNumber(0, 0);

        #endregion

        #region Fields

        private static readonly string[] EXT_STRS =
            {WITH_EXTERNALS_STR, WITHOUT_EXTERNALS_STR, UNKNOWN_EXTERNALS_STR};

        private readonly ExternalsType _Externals = ExternalsType.UNKNOWN;

        private readonly int _Major;

        private readonly int _Minor;

        private readonly StabilityType _Stability = StabilityType.UNKNOWN;


       
        static readonly string[] STABILITY_STRS = 
            { STABLE_STR, UNSTABLE_STR, UNKNOWN_STABILITY_STR };


        #endregion

        #region Constructors


        public VersionNumber(ExternalsType Externals, int Major, int Minor, StabilityType Stable)
        {
            _Major = Major;
            _Minor = Minor;
            _Externals = Externals;
            _Stability = Stable;
        }

        public VersionNumber(ExternalsType Externals, int Major, int Minor)
            : this (Externals, Major, Minor, StabilityType.UNKNOWN)
        {
            _Major = Major;
            _Minor = Minor;
            _Externals = Externals;
        }

        public VersionNumber(int Major)
            : this(ExternalsType.UNKNOWN, Major, 0, StabilityType.UNKNOWN)
        {
        }

        public VersionNumber(int Major, int Minor)
            : this(ExternalsType.UNKNOWN, Major, Minor, StabilityType.UNKNOWN)
        {
        }

        public VersionNumber(int Major, int Minor, StabilityType Stable)
            : this(ExternalsType.UNKNOWN, Major, Minor, Stable)
        {
        }

        #endregion

        #region Operators

        public static bool operator ==(VersionNumber a, VersionNumber b)
        {
            return a.Compare(b) == compare.EQUAL;
        }

        public static bool operator >(VersionNumber a, VersionNumber b)
        {
            return a.Compare(b) == compare.GREATER;
        }

        public static bool operator !=(VersionNumber a, VersionNumber b)
        {
            return !(a.Compare(b) == compare.EQUAL);
        }

        public static bool operator <(VersionNumber a, VersionNumber b)
        {
            return a.Compare(b) == compare.LESSER;
        }

        #endregion

        #region Properties

        public ExternalsType Externals
        {
            get { return _Externals; }
        }

        public StabilityType Stability
        {
            get { return _Stability; }
        }

        public int Major
        {
            get { return _Major; }
        }

        public int Minor
        {
            get { return _Minor; }
        }

        #endregion

        #region Public Methods

        #region IComparable Members

        public int CompareTo(object obj)
        {
            VersionNumber v = obj as VersionNumber;
            if (v != null)
            {
                compare c = Compare(v);
                if (c == compare.GREATER)
                    return 1;

                if (c == compare.LESSER)
                    return -1;

                return 0;
            }

            return 1;
        }

        #endregion

        #region IFormattable Members

        public String ToString(String format, IFormatProvider fp)
        {
            if (format == null)
            {
                if (_Minor > 0)
                    return String.Format("{0}.{1}", _Major, _Minor);

                return String.Format("{0}", _Major);
            }

            if (format == "e")
            {
                if (_Major == 0)
                    return "n/a";

                if (_Minor > 0)
                    return String.Format("{2}{0}.{1}{3}", _Major, _Minor,
                                         EXT_STRS[(int) _Externals],
                                         STABILITY_STRS[(int)_Stability]);

                return String.Format("{1}{0}{2}", _Major, EXT_STRS[(int) _Externals], STABILITY_STRS[(int)_Stability]);
            }

            if (format == "E")
            {
                if (_Major == 0)
                    return "n/a";

                if (_Minor > 0)
                    return String.Format("{2} Externals {0}.{1} {3}", _Major, _Minor,
                                         _Externals, _Stability);

                return String.Format("{1} Externals {0} {2} ", _Major, _Externals, _Stability);
            }

            // For any unrecognized format, throw an exception.
            throw new FormatException(String.Format("Invalid format string: '{0}'.", format));
        }

        #endregion

        public override bool Equals(object o)
        {
            VersionNumber v = o as VersionNumber;
            if (v != null)
                return Compare(v) == compare.EQUAL;

            return false;
        }

        public override int GetHashCode()
        {
            return (((int) _Externals) << 30 + _Major << 14 + _Minor);
        }

        public static VersionNumber Parse(string s)
        {
            return Parse(s, ExternalsType.UNKNOWN);
        }

        public static VersionNumber Parse(string s, ExternalsType ext)
        {
            return Parse(s, ext, StabilityType.UNKNOWN);
        }

        public static VersionNumber Parse(string s, ExternalsType ext, StabilityType stab)
        {
            int min = 0, maj = 0;
            char[] d = {'.'};

            string[] ss = s.Split(d);

            if (ss.GetLength(0) > 0)
            {
                int i = ss.GetLowerBound(0);

                maj = Int32.Parse(ss[i]);

                if (ss.GetLength(0) > 1)
                    min = Int32.Parse(ss[++i]);
            }

            return new VersionNumber(ext, maj, min, stab);
        }

        public override String ToString()
        {
            return ToString(null, null);
        }

        #endregion

        #region Private Methods

        // returns this ><= with, 
        private compare Compare(VersionNumber with)
        {
            if (ReferenceEquals(with, null))
                return compare.GREATER;

            compare c;

            // Compare same externals type
            if (_Externals == with._Externals)
                c = CompareNumber(with);
            else
            {
                // first try this, if the two modes differ then
                // return less than. This assumes less than means
                // update.
                c = Compare(_Major, with._Major);
                if (c == compare.EQUAL)
                {
                    if (with._Externals == ExternalsType.UNKNOWN)
                        c = compare.GREATER;
                    else if (_Externals == ExternalsType.UNKNOWN)
                        c = compare.LESSER;
                    else
                        c = compare.GREATER;
                }
            }

            //           Program.LogDbg(string.Format("Ver Compare: {0:e} {1} {2:e}", this, c, with));
            return c;
        }

        private static compare Compare(int v1, int v2)
        {
            if (v1 > v2)
                return compare.GREATER;
            
            if (v1 < v2)
                return compare.LESSER;

            return compare.EQUAL;
        }

        private compare CompareNumber(VersionNumber with)
        {
            compare c = Compare(_Major, with._Major);
            if (c == compare.EQUAL)
                c = Compare(_Minor, with._Minor);
            return c;
        }

        #endregion
    }
}